{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：== 和 is的区别"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "我们都知道 Python 中一切皆对象，对象的三个基本要素：id(内存中的地址)，type(类型)，value(值)。判断对象与对象是否相等可以用 == 也可以用 is。下面我们具体来看下这两者之间的区别在哪。\n",
    "\n",
    "答案\n",
    "is 比较的是两个对象的 id 值，也就是比较它们在内存中的地址是否相等。== 则是比较两个对象的值是否相等。\n",
    "\n",
    ">>> a = [1,2,3]\n",
    ">>> b = a\n",
    ">>> a is b\n",
    "True\n",
    ">>> b == a\n",
    "True\n",
    ">>> b = a[:]\n",
    ">>> b is a \n",
    "False\n",
    ">>> b == a\n",
    "True\n",
    "拓展\n",
    "在上面的代码中为什么经过 b = a[:] 之后 b is a 就是 False 了呢？\n",
    "\n",
    "这要从 Python 的赋值语法说起，在理解 Python 的赋值语句的时候，我们从等式的右边读起。可以将它理解为先创建一个空列表的对象，对象一旦创建，对象的内存地址不会改变，然后把左边的变量绑定到右边的对象上。\n",
    "\n",
    "你可以简单的理解为 b = a 是把 a 在内存中的地址一并赋值给了 b，这时a 和 b 在内存中的地址是一样的。所以 b is a 是 True。但是经过经过 b = a[:] 之后相当于在内存中重新创建了一个新的 b ，它们两个的id值不一样了，自然就是 False。\n",
    "\n",
    "加分点\n",
    "我们只考虑了 is 和 == 之间的区别，你考虑过什么情况下 is 和 == 之间的结果是完全一样的吗？\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题2：双下划线和单下划线有什么意义吗"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "答案：\n",
    "\n",
    "_single_leading_underscore：具有前导下划线的类中的名称只是向其他程序员指示该属性或方法是私有的。这是一种默认的惯例。不能用from module import *导入，其他方面和公有一样访问。\n",
    "\n",
    "__double_leading_underscore：在命名一个类属性时，调用名称修改，比如：在类FooBar中，类属性__boo会变为_FooBar__boo。\n",
    "\n",
    "__double_leading_and_trailing_underscore__：常见于Python内置的“魔术”对象或属性，例如，__init__，__import__或__file__，为了区别于用户自定义的__double_leading_underscore。它们会被Python系统调用的，不用用户自己调用。\n",
    "\n",
    "问题中涉及的知识点：\n",
    "\n",
    "基本的PEP8规范\n",
    "私有属性的命名\n",
    "知识点解析：\n",
    "\n",
    "Python不通过语言特性去封装数据，而是通过一些命名规约来达到此目的。\n",
    "\n",
    "前导单下划线\n",
    "\n",
    "这是一种指示内部使用的惯例约定。但是，Python并不会阻止任何人去访问带前导单下划线的属性，它仍可以被外界获取。这个惯例约定同样适用于模块，函数的命名。\n",
    "\n",
    "前导双下划线\n",
    "\n",
    "前导双下划线的属性或方法名会改变成其它形式。调用名称修改的目的就是为了继承，为类提供一种简单的方法来定义“私有”实例变量和方法，而不必担心派生类定义的实例变量，或者通过类外部的代码来修改实例变量。\n",
    "\n",
    "双向双下划线\n",
    "\n",
    "一般用于Python自己内置的方法属性。\n",
    "\n",
    ">>> class MyClass():\n",
    "...     def __init__(self):\n",
    "...         self.__superprivate = \"Hello\"\n",
    "...         self._semiprivate = \", world!\"\n",
    "...     def __add__(self, new_str):\n",
    "...         return \" \".join([self.__superprivate, new_str])\n",
    "...\n",
    ">>> mc = MyClass()\n",
    ">>> print(mc.__superprivate)\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "AttributeError: myClass instance has no attribute '__superprivate'\n",
    ">>> print(mc._semiprivate)\n",
    ", world!\n",
    ">>> print(mc.__dict__)\n",
    "{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}\n",
    ">>> print(mc + \"xxx\")\n",
    "Hello xxx\n",
    "在上面这个例子中，虽然我们认为_semiprivate是私有的，但我们还是可以直接调用，Python并没有真正的支持私有化，前导单下划线的私有性只是一种编程规范。就比如，按PEP8规定，缩进应该使用4个空格，但是我们使用tab去缩进，Python还是可以运行成功的。\n",
    "\n",
    "但是，我们无法直接使用mc.__superprivate，因为类属性__superprivate已经更名为_MyClass__superprivate。这样的好处是，假象一下，你有一个子类MySubClass，它的私有属性就会重命名为_MySubClass__superprivate，而父类的私有属性就不会被覆盖。去修改子类的私有属性而不会去影响到父类。\n",
    "\n",
    "__add__是魔法方法，它是有Python系统自己调用。上例中，我们没有显式地调用它，而是使用运算符mc + \"xxx\"，而其背后，Python就隐式调用了__add__。\n",
    "\n",
    "Tips: 如果你定义的变量与某个保留关键字冲突，可以使用后置单下划线。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}